#pragma once

#include <list>
#include <hash_map>
#include <hash_set>
#include <assert.h>

#include "SenetBoardDraft.h"
#include "HeuristicParams.h"
#include "SenetPlayerExt.h"
#include "CommonApps.h"
#include "SticksProbability.h"

// #define LOWER_BOUND -5
// #define UPPER_BOUND 5

/************************************************************************/
/*  Implements all the Heuristics                                       */
/************************************************************************/
class SenetHeuristics
{
public:
	SenetHeuristics();
	void init(
		const SenetPlayerExt* owner,
		const SticksProbability& sticksProbability);
	
	~SenetHeuristics()
	{
		delete[] m_OwnerMovesPerStick;
		delete[] m_OpponentMovesPerStick;
	}

	float score(const SenetBoardDraft& board);
//	float score2(const SenetBoardDraft& board);

	float score(const SenetBoardDraft& board, Definitions::PlayerColor currentPlayerColor)
	{
		m_CurrentPlayerColor = currentPlayerColor;
		m_OpponentPlayerColor = Definitions::getOppositePlayer(currentPlayerColor);
		return score(board);
	}

	float scoreMini(const SenetBoardDraft& board);

	// TODO: make this methods use compile time constants
	inline float getUpperHeuristicBound()
	{
		return m_HeuristicParams.m_CoefficientsSum;
	}
	float getLowerHeuristicBound()
	{
		return 0.0f;
		//-1.0f * m_HeuristicParams.m_CoefficientsSum;
	}


// 	void setHeuristicParams(const HeuristicParams& params)
// 	{
// 		m_HeuristicParams = params;
// 	}

	int static const totalSubHeuristics = HeuristicParams::numSubHeuristics * HeuristicParams::numPlayers;
	int static const Ha = 0;
	int static const Hb = 1;
protected:
	void setBoard(const SenetBoardDraft& board);
	void setOwner(const SenetPlayerExt* owner);
private:
	void calch1squaresMaxDistance(int numSquares, int numPieces);
	void calch2maximumMovesNumber(int numSquares, int numPieces);

//	void calcBoardDependentMembers();
	void calcBoardDependentMembers2();
//	void updateSafePositionList(const SenetBoard& initialBoard);
//	bool isCanMove(Definitions::PlayerColor player,	int fromSquare, int sticks);

	//////////////////////////////////////////////////////////////////////////
	///			sub-heuristics methods
	//////////////////////////////////////////////////////////////////////////

	//////////////////////////////////////////////////////////////////////////
	// h2 - Manhattan distance
	// param:
	//     isCheckingOwner - true if the owner is being checked, false otherwise
	// return:
	//     float in the range [0, 1] that is the score of the heuristics
	//////////////////////////////////////////////////////////////////////////
	float h1Score(bool isCheckingOwner);

	//////////////////////////////////////////////////////////////////////////
	// h2 - Number of possible moves
	// param:
	//     isCheckingOwner - true if the owner is being checked, false otherwise
	// return:
	//     float in the range [0, 1] that is the score of the heuristics
	//////////////////////////////////////////////////////////////////////////
	float h2Score(bool isCheckingOwner);

	//////////////////////////////////////////////////////////////////////////
	// h3 - Number of safe positions
	// param:
	//     isCheckingOwner - true if the owner is being checked, false otherwise
	// return:
	//     float in the range [0, 1] that is the score of the heuristics
	//////////////////////////////////////////////////////////////////////////
	float h3Score(bool isCheckingOwner);

	//////////////////////////////////////////////////////////////////////////
	// h4 - Number of current pieces
	// param:
	//     isCheckingOwner - true if the owner is being checked, false otherwise
	// return:
	//     float in the range [0, 1] that is the score of the heuristics
	//////////////////////////////////////////////////////////////////////////
	float h4Score(bool isCheckingOwner);

	//////////////////////////////////////////////////////////////////////////
	// h5 - Number of sequences of 2 (or more)
	// param:
	//     isCheckingOwner - true if the owner is being checked, false otherwise
	// return:
	//     float in the range [0, 1] that is the score of the heuristics
	//////////////////////////////////////////////////////////////////////////
	float h5Score(bool isCheckingOwner);

	//////////////////////////////////////////////////////////////////////////
	// h6 - Number of sequences of 3 (or more)
	// param:
	//     isCheckingOwner - true if the owner is being checked, false otherwise
	// return:
	//     float in the range [0, 1] that is the score of the heuristics
	//////////////////////////////////////////////////////////////////////////
	float h6Score(bool isCheckingOwner);

	//////////////////////////////////////////////////////////////////////////
	// h7 - Threat amount
	// param:
	//     isCheckingOwner - true if the owner is being checked, false otherwise
	// return:
	//     float in the range [0, 1] that is the score of the heuristics
	//////////////////////////////////////////////////////////////////////////
	float h7Score(bool isCheckingOwner);

	inline float h1aScore()
	{
		return h1Score(true);
	}
	inline float h1bScore()
	{
		return h1Score(false);
	}
	inline float h2aScore()
	{
		return h2Score(true);
	}
	inline float h2bScore()
	{
		return h2Score(false);
	}
	inline float h3aScore()
	{
		return h3Score(true);
	}
	inline float h3bScore()
	{
		return h3Score(false);
	}
	inline float h4aScore()
	{
		return h4Score(true);
	}
	inline float h4bScore()
	{
		return h4Score(false);
	}
	inline float h5aScore()
	{
		return h5Score(true);
	}
	inline float h5bScore()
	{
		return h5Score(false);
	}
	inline float h6aScore()
	{
		return h6Score(true);
	}
	inline float h6bScore()
	{
		return h6Score(false);
	}
	inline float h7aScore()
	{
		return h7Score(true);
	}
	inline float h7bScore()
	{
		return h7Score(false);
	}


	//////////////////////////////////////////////////////////////////////////
	// Assumes: positions.size() > 0
	//////////////////////////////////////////////////////////////////////////
	int getSeqNum(const vector<int>& positions, int minimumSeq)
	{
		assert(positions.size() > 0);
		int n = positions.size();
		int totalSeqs = 0;
		int currentSeqNumber = 0;
		bool seqStarted = false;
		int lastPosition = positions[0];
		for (int i = 1; i < n; i++)
		{
			if (lastPosition + 1 == positions[i] /* in a sequence */)
			{
				if (seqStarted)
				{
					// in this case - we are already counting the
					// current sequence
					continue;
				}
				else
				{
					if (currentSeqNumber + 1 == minimumSeq)
					{
						// a sequence was found
						seqStarted = true;
						++totalSeqs;
					}
					else
					{
						// the sequence is too short to be counted
						++currentSeqNumber;
					}
				}
			}
			else
			{
				seqStarted = false;
				currentSeqNumber = 0;
			}
			lastPosition = positions[i];
		}
		return totalSeqs;	
	}

	int getManhattanDistance(const vector<int>& positions) const
	{
		int n = positions.size();
		int distance = 0;
		for (int i = 0; i < n; i++)
		{
			distance += ((m_SenetBoard->getBoardSize()) - positions[i]);
		}
		return distance;
	}

	int getSafePositions(const vector<int>& positions) const
	{
		int n = positions.size();
		int safePositionsNum = 0;
		for (int i = 0; i < n; i++)
		{
			if (m_SenetBoard->isSafePosition(positions[i]))
			{
				++safePositionsNum;
			}
		}
		return safePositionsNum;
	}

	inline int getSequenceSize(Definitions::PlayerColor player, int startIndex)
	{
		int boardSize = m_SenetBoard->getBoardSize();
			//m_Owner->getSizeSquares();
		int currentSequenceSize = 0;
		for (int i = startIndex; i < boardSize; i++)
		{
			const SenetBoard::SquareData& currentSquareData = (*m_SenetBoard)[i];
			if (currentSquareData.player_color == player)
			{
				currentSequenceSize++;
			}
			else
			{
				break;
			}
		}
		return currentSequenceSize;
	}



	//////////////////////////////////////////////////////////////////////////
	// a method to normalize the score of the heuristic.
	// the method works the following way:
	// let a0, ..., a13 the coefficients of the sub heuristics
	// let h1a,h2b, ..., h7b the values for the sub-heuristics
	// the normalized value of the heuristic will be:
	// a0*h1a + a1*h2b+ .... + a13*h7b
	// no division is made to increase performances.
	// param:
	//     scoreVector - the vector that holds the result of the sub-heuristics
	// return:
	//     float in the range [0, numSubHeuristics X numPlayers] that is the score the heuristic
	//////////////////////////////////////////////////////////////////////////
	inline float normalizeScoring(const float scoreVector[HeuristicParams::numSubHeuristics][HeuristicParams::numPlayers])
	{
		float score = 0.0f;
		for (int i = 0; i < HeuristicParams::numSubHeuristics; ++i)
		{
			for (int j = 0; j < HeuristicParams::numPlayers; ++j)
			{
				// TODO: Add coefficients 
				score += (m_HeuristicParams.m_Coefficients[i][j] * scoreVector[i][j]);
			}
		}
		return score;
	}

	inline float getTerminalScoring()
	{
		bool ownerWon;
		// test if current player won
		if (m_CurrentPlayerColor == Definitions::WHITE)
		{
			ownerWon = (m_SenetBoard->whitePiecesNum() == 0);
		}
		else // Owner is black)
		{
			ownerWon = (m_SenetBoard->blackPiecesNum() == 0);
		}
		return (ownerWon ? getUpperHeuristicBound() : getLowerHeuristicBound());
	}

// 	inline bool isJumpingBlockSequence(int fromSquare, int steps, const stdext::hash_set<int>& sequence3)
// 	{
// 		int start = fromSquare + 1;
// 		int end = fromSquare + steps - 1;
// 		for (int i = start; i < end; i++)
// 		{
// 			if (CommonApps::hash_setContains(sequence3, i))
// 			{
// 				return true;
// 			}
// 		}
// 		return false;
// 	}
	float getTotalPossibleMoves(bool isCheckingOwner);

	inline float getThreatAmount(bool isCheckingOwner)
	{
		float totalThreat = 0.0f;
		vector<int>* possibleMoves = (isCheckingOwner ? m_OwnerMovesPerStick : m_OpponentMovesPerStick);
		Definitions::PlayerColor opponentColor = (isCheckingOwner ? m_OpponentPlayerColor : m_CurrentPlayerColor);
		int sticks = m_SticksNumber;
		for (int i = 1; i <= sticks; ++i)
		{
			const vector<int>& currentPlayerMovesStick = possibleMoves[i];
			int curThreatCount = getThreatCount(currentPlayerMovesStick, i, opponentColor);
			totalThreat += (m_SticksProbability->getProbability(i) * (float)curThreatCount);
		}
		return totalThreat;
	}

	inline int getThreatCount(
		const vector<int>& currentPlayerMovesStick,
		int stick,
		Definitions::PlayerColor opponentColor)
	{
		int totalThreats = 0;
		int n = currentPlayerMovesStick.size();
		for (int i = 0; i < n; ++i)
		{
			int currentPos = currentPlayerMovesStick[i];
			int destPos = currentPos + stick;
			if (destPos < m_SenetBoard->getBoardSize() &&
				(*m_SenetBoard)[destPos].player_color == opponentColor)
			{
				// we will land on the opponent
				++totalThreats;
			}
		}
		return totalThreats;
	}
	
	// heuristic based members
	HeuristicParams m_HeuristicParams;

	// game related members
	const SenetBoardDraft* m_SenetBoard;
	int m_SticksNumber;
//TODO: remove public	
public:
		const SenetPlayerExt* m_Owner;
private:

	const SticksProbability* m_SticksProbability;
//	HeuristicParams m_HeuristicsParams;
	float m_InitialPiecesNumber;
	float m_InitialPiecesNumberDiv2;
	float m_InitialPiecesNumberDiv3;

	// game-dependent heuristics-related members
	float m_h1ManhattanMaxDistance;
	float m_h2MaximumMovesNumber;
//	std::list<int> m_SafePositions;
	float m_SafePositionsNumber;

	// board-dependent heuristics-related members
	// the members will be updated at each call to score()
	// by the method: calcBoardDependentMembers()

	Definitions::PlayerColor m_CurrentPlayerColor;
	Definitions::PlayerColor m_OpponentPlayerColor;

	// the positions of the heuristic owner
	std::vector<int> m_PositionsOwner;
	
	// the positions of the opponent
	std::vector<int> m_PositionsOpponent;

	// the Manhattan distance of the owner
	int m_OwnerManhattanDistance;

	// the Manhattan distance of the opponent
	int m_OpponentManhattanDistance;

	// the moves that the owner can make (for each stick)
	std::vector<int>* m_OwnerMovesPerStick;	

	// the moves that the opponent can make (for each stick)
	std::vector<int>* m_OpponentMovesPerStick;

	// the number of safe positions of the owner
	int m_OwnerSafePositionsNum;

	// the number of safe positions of the opponent
	int m_OpponentSafePositionsNum;

	// number of sequences of 2 (or more) of the owner
	int m_NumSequence2Owner;

	// number of sequences of 2 (or more) of the opponent
	int m_NumSequence2Opponent;

	// number of sequences of 3 (or more) of the owner
	int m_NumSequence3Owner;

	// number of sequences of 3 (or more) of the opponent
	int m_NumSequence3Opponent;

	// the amount of threat the owner has on the opponent
	float m_ThreatValueOwner;
	
	// the amount of threat the opponent has on the owner
	float m_ThreatValueOpponent;

};
